home *** CD-ROM | disk | FTP | other *** search
/ EuroCD 3 / EuroCD 3.iso / Programming / Python-1.4 / Lib / urllib.py < prev    next >
Text File  |  1998-06-24  |  22KB  |  761 lines

  1. # Open an arbitrary URL
  2. #
  3. # See the following document for a tentative description of URLs:
  4. #     Uniform Resource Locators              Tim Berners-Lee
  5. #     INTERNET DRAFT                                    CERN
  6. #     IETF URL Working Group                    14 July 1993
  7. #     draft-ietf-uri-url-01.txt
  8. #
  9. # The object returned by URLopener().open(file) will differ per
  10. # protocol.  All you know is that is has methods read(), readline(),
  11. # readlines(), fileno(), close() and info().  The read*(), fileno()
  12. # and close() methods work like those of open files. 
  13. # The info() method returns an mimetools.Message object which can be
  14. # used to query various info about the object, if available.
  15. # (mimetools.Message objects are queried with the getheader() method.)
  16.  
  17. import string
  18. import socket
  19. import regex
  20. import os
  21.  
  22.  
  23. __version__ = '1.5'
  24.  
  25. # Helper for non-unix systems
  26. if os.name == 'mac':
  27.     from macurl2path import url2pathname, pathname2url
  28. elif os.name == 'nt':    
  29.     from nturl2path import url2pathname, pathname2url 
  30. else:
  31.     def url2pathname(pathname):
  32.         return pathname
  33.     def pathname2url(pathname):
  34.         return pathname
  35.  
  36. # This really consists of two pieces:
  37. # (1) a class which handles opening of all sorts of URLs
  38. #     (plus assorted utilities etc.)
  39. # (2) a set of functions for parsing URLs
  40. # XXX Should these be separated out into different modules?
  41.  
  42.  
  43. # Shortcut for basic usage
  44. _urlopener = None
  45. def urlopen(url):
  46.     global _urlopener
  47.     if not _urlopener:
  48.         _urlopener = FancyURLopener()
  49.     return _urlopener.open(url)
  50. def urlretrieve(url, filename=None):
  51.     global _urlopener
  52.     if not _urlopener:
  53.         _urlopener = FancyURLopener()
  54.     if filename:
  55.         return _urlopener.retrieve(url, filename)
  56.     else:
  57.         return _urlopener.retrieve(url)
  58. def urlcleanup():
  59.     if _urlopener:
  60.         _urlopener.cleanup()
  61.  
  62.  
  63. # Class to open URLs.
  64. # This is a class rather than just a subroutine because we may need
  65. # more than one set of global protocol-specific options.
  66. # Note -- this is a base class for those who don't want the
  67. # automatic handling of errors type 302 (relocated) and 401
  68. # (authorization needed).
  69. ftpcache = {}
  70. class URLopener:
  71.  
  72.     # Constructor
  73.     def __init__(self, proxies=None):
  74.         if proxies is None:
  75.             proxies = getproxies()
  76.         self.proxies = proxies
  77.         server_version = "Python-urllib/%s" % __version__
  78.         self.addheaders = [('User-agent', server_version)]
  79.         self.tempcache = None
  80.         # Undocumented feature: if you assign {} to tempcache,
  81.         # it is used to cache files retrieved with
  82.         # self.retrieve().  This is not enabled by default
  83.         # since it does not work for changing documents (and I
  84.         # haven't got the logic to check expiration headers
  85.         # yet).
  86.         self.ftpcache = ftpcache
  87.         # Undocumented feature: you can use a different
  88.         # ftp cache by assigning to the .ftpcache member;
  89.         # in case you want logically independent URL openers
  90.  
  91.     def __del__(self):
  92.         self.close()
  93.  
  94.     def close(self):
  95.         self.cleanup()
  96.  
  97.     def cleanup(self):
  98.         import os
  99.         if self.tempcache:
  100.             for url in self.tempcache.keys():
  101.                 try:
  102.                     os.unlink(self.tempcache[url][0])
  103.                 except os.error:
  104.                     pass
  105.                 del self.tempcache[url]
  106.  
  107.     # Add a header to be used by the HTTP interface only
  108.     # e.g. u.addheader('Accept', 'sound/basic')
  109.     def addheader(self, *args):
  110.         self.addheaders.append(args)
  111.  
  112.     # External interface
  113.     # Use URLopener().open(file) instead of open(file, 'r')
  114.     def open(self, fullurl):
  115.         fullurl = unwrap(fullurl)
  116.         type, url = splittype(fullurl)
  117.          if not type: type = 'file'
  118.         self.openedurl = '%s:%s' % (type, url)
  119.         if self.proxies.has_key(type):
  120.             proxy = self.proxies[type]
  121.             type, proxy = splittype(proxy)
  122.             host, selector = splithost(proxy)
  123.             url = (host, fullurl) # Signal special case to open_*()
  124.         name = 'open_' + type
  125.         if '-' in name:
  126.             import regsub
  127.             name = regsub.gsub('-', '_', name)
  128.         if not hasattr(self, name):
  129.             return self.open_unknown(fullurl)
  130.         try:
  131.             return getattr(self, name)(url)
  132.         except socket.error, msg:
  133.             raise IOError, ('socket error', msg)
  134.  
  135.     # Overridable interface to open unknown URL type
  136.     def open_unknown(self, fullurl):
  137.         type, url = splittype(fullurl)
  138.         raise IOError, ('url error', 'unknown url type', type)
  139.  
  140.     # External interface
  141.     # retrieve(url) returns (filename, None) for a local object
  142.     # or (tempfilename, headers) for a remote object
  143.     def retrieve(self, url, filename=None):
  144.         if self.tempcache and self.tempcache.has_key(url):
  145.             return self.tempcache[url]
  146.         url1 = unwrap(url)
  147.         self.openedurl = url1
  148.         if self.tempcache and self.tempcache.has_key(url1):
  149.             self.tempcache[url] = self.tempcache[url1]
  150.             return self.tempcache[url1]
  151.         type, url1 = splittype(url1)
  152.         if not filename and (not type or type == 'file'):
  153.             try:
  154.                 fp = self.open_local_file(url1)
  155.                 del fp
  156.                 return url2pathname(splithost(url1)[1]), None
  157.             except IOError, msg:
  158.                 pass
  159.         fp = self.open(url)
  160.         headers = fp.info()
  161.         if not filename:
  162.             import tempfile
  163.             filename = tempfile.mktemp()
  164.         result = filename, headers
  165.         if self.tempcache is not None:
  166.             self.tempcache[url] = result
  167.         tfp = open(filename, 'w')
  168.         bs = 1024*8
  169.         block = fp.read(bs)
  170.         while block:
  171.             tfp.write(block)
  172.             block = fp.read(bs)
  173.         del fp
  174.         del tfp
  175.         return result
  176.  
  177.     # Each method named open_<type> knows how to open that type of URL
  178.  
  179.     # Use HTTP protocol
  180.     def open_http(self, url):
  181.         import httplib
  182.         if type(url) is type(""):
  183.             host, selector = splithost(url)
  184.             user_passwd, host = splituser(host)
  185.         else:
  186.             host, selector = url
  187.             urltype, rest = splittype(selector)
  188.             if string.lower(urltype) == 'http':
  189.                 realhost, rest = splithost(rest)
  190.                 user_passwd, realhost = splituser(realhost)
  191.                 if user_passwd:
  192.                 selector = "%s://%s%s" % (urltype,
  193.                               realhost, rest)
  194.             print "proxy via http:", host, selector
  195.         if not host: raise IOError, ('http error', 'no host given')
  196.         if user_passwd:
  197.             import base64
  198.             auth = string.strip(base64.encodestring(user_passwd))
  199.         else:
  200.             auth = None
  201.         h = httplib.HTTP(host)
  202.         h.putrequest('GET', selector)
  203.         if auth: h.putheader('Authorization: Basic %s' % auth)
  204.         for args in self.addheaders: apply(h.putheader, args)
  205.         h.endheaders()
  206.         errcode, errmsg, headers = h.getreply()
  207.         fp = h.getfile()
  208.         if errcode == 200:
  209.             return addinfourl(fp, headers, self.openedurl)
  210.         else:
  211.             return self.http_error(url,
  212.                            fp, errcode, errmsg, headers)
  213.  
  214.     # Handle http errors.
  215.     # Derived class can override this, or provide specific handlers
  216.     # named http_error_DDD where DDD is the 3-digit error code
  217.     def http_error(self, url, fp, errcode, errmsg, headers):
  218.         # First check if there's a specific handler for this error
  219.         name = 'http_error_%d' % errcode
  220.         if hasattr(self, name):
  221.             method = getattr(self, name)
  222.             result = method(url, fp, errcode, errmsg, headers)
  223.             if result: return result
  224.         return self.http_error_default(
  225.             url, fp, errcode, errmsg, headers)
  226.  
  227.     # Default http error handler: close the connection and raises IOError
  228.     def http_error_default(self, url, fp, errcode, errmsg, headers):
  229.         void = fp.read()
  230.         fp.close()
  231.         raise IOError, ('http error', errcode, errmsg, headers)
  232.  
  233.     # Use Gopher protocol
  234.     def open_gopher(self, url):
  235.         import gopherlib
  236.         host, selector = splithost(url)
  237.         if not host: raise IOError, ('gopher error', 'no host given')
  238.         type, selector = splitgophertype(selector)
  239.         selector, query = splitquery(selector)
  240.         selector = unquote(selector)
  241.         if query:
  242.             query = unquote(query)
  243.             fp = gopherlib.send_query(selector, query, host)
  244.         else:
  245.             fp = gopherlib.send_selector(selector, host)
  246.         return addinfourl(fp, noheaders(), self.openedurl)
  247.  
  248.     # Use local file or FTP depending on form of URL
  249.     def open_file(self, url):
  250.         if url[:2] == '//':
  251.             return self.open_ftp(url)
  252.         else:
  253.             return self.open_local_file(url)
  254.  
  255.     # Use local file
  256.     def open_local_file(self, url):
  257.         host, file = splithost(url)
  258.         if not host:
  259.             return addinfourl(open(url2pathname(file), 'r'), noheaders(), 'file:'+file)
  260.         host, port = splitport(host)
  261.         if not port and socket.gethostbyname(host) in (
  262.               localhost(), thishost()):
  263.             file = unquote(file)
  264.             return addinfourl(open(url2pathname(file), 'r'), noheaders(), 'file:'+file)
  265.         raise IOError, ('local file error', 'not on local host')
  266.  
  267.     # Use FTP protocol
  268.     def open_ftp(self, url):
  269.         host, path = splithost(url)
  270.         if not host: raise IOError, ('ftp error', 'no host given')
  271.         host, port = splitport(host)
  272.         user, host = splituser(host)
  273.         if user: user, passwd = splitpasswd(user)
  274.         else: passwd = None
  275.         host = socket.gethostbyname(host)
  276.         if not port:
  277.             import ftplib
  278.             port = ftplib.FTP_PORT
  279.         path, attrs = splitattr(path)
  280.         dirs = string.splitfields(path, '/')
  281.         dirs, file = dirs[:-1], dirs[-1]
  282.         if dirs and not dirs[0]: dirs = dirs[1:]
  283.         key = (user, host, port, string.joinfields(dirs, '/'))
  284.         try:
  285.             if not self.ftpcache.has_key(key):
  286.                 self.ftpcache[key] = \
  287.                            ftpwrapper(user, passwd,
  288.                                   host, port, dirs)
  289.             if not file: type = 'D'
  290.             else: type = 'I'
  291.             for attr in attrs:
  292.                 attr, value = splitvalue(attr)
  293.                 if string.lower(attr) == 'type' and \
  294.                    value in ('a', 'A', 'i', 'I', 'd', 'D'):
  295.                     type = string.upper(value)
  296.             return addinfourl(self.ftpcache[key].retrfile(file, type),
  297.                   noheaders(), self.openedurl)
  298.         except ftperrors(), msg:
  299.             raise IOError, ('ftp error', msg)
  300.  
  301.  
  302. # Derived class with handlers for errors we can handle (perhaps)
  303. class FancyURLopener(URLopener):
  304.  
  305.     def __init__(self, *args):
  306.         apply(URLopener.__init__, (self,) + args)
  307.         self.auth_cache = {}
  308.  
  309.     # Default error handling -- don't raise an exception
  310.     def http_error_default(self, url, fp, errcode, errmsg, headers):
  311.         return addinfourl(fp, headers, self.openedurl)
  312.  
  313.     # Error 302 -- relocated (temporarily)
  314.     def http_error_302(self, url, fp, errcode, errmsg, headers):
  315.         # XXX The server can force infinite recursion here!
  316.         if headers.has_key('location'):
  317.             newurl = headers['location']
  318.         elif headers.has_key('uri'):
  319.             newurl = headers['uri']
  320.         else:
  321.             return
  322.         void = fp.read()
  323.         fp.close()
  324.         return self.open(newurl)
  325.  
  326.     # Error 301 -- also relocated (permanently)
  327.     http_error_301 = http_error_302
  328.  
  329.     # Error 401 -- authentication required
  330.     # See this URL for a description of the basic authentication scheme:
  331.     # http://www.ics.uci.edu/pub/ietf/http/draft-ietf-http-v10-spec-00.txt
  332.     def http_error_401(self, url, fp, errcode, errmsg, headers):
  333.         if headers.has_key('www-authenticate'):
  334.             stuff = headers['www-authenticate']
  335.             p = regex.compile(
  336.                 '[ \t]*\([^ \t]+\)[ \t]+realm="\([^"]*\)"')
  337.             if p.match(stuff) >= 0:
  338.                 scheme, realm = p.group(1, 2)
  339.                 if string.lower(scheme) == 'basic':
  340.                     return self.retry_http_basic_auth(
  341.                         url, realm)
  342.  
  343.     def retry_http_basic_auth(self, url, realm):
  344.         host, selector = splithost(url)
  345.         i = string.find(host, '@') + 1
  346.         host = host[i:]
  347.         user, passwd = self.get_user_passwd(host, realm, i)
  348.         if not (user or passwd): return None
  349.         host = user + ':' + passwd + '@' + host
  350.         newurl = '//' + host + selector
  351.         return self.open_http(newurl)
  352.  
  353.     def get_user_passwd(self, host, realm, clear_cache = 0):
  354.         key = realm + '@' + string.lower(host)
  355.         if self.auth_cache.has_key(key):
  356.             if clear_cache:
  357.                 del self.auth_cache[key]
  358.             else:
  359.                 return self.auth_cache[key]
  360.         user, passwd = self.prompt_user_passwd(host, realm)
  361.         if user or passwd: self.auth_cache[key] = (user, passwd)
  362.         return user, passwd
  363.  
  364.     def prompt_user_passwd(self, host, realm):
  365.         # Override this in a GUI environment!
  366.         try:
  367.             user = raw_input("Enter username for %s at %s: " %
  368.                      (realm, host))
  369.             self.echo_off()
  370.             try:
  371.                 passwd = raw_input(
  372.                   "Enter password for %s in %s at %s: " %
  373.                   (user, realm, host))
  374.             finally:
  375.                 self.echo_on()
  376.             return user, passwd
  377.         except KeyboardInterrupt:
  378.             return None, None
  379.  
  380.     def echo_off(self):
  381.         import os
  382.         os.system("stty -echo")
  383.  
  384.     def echo_on(self):
  385.         import os
  386.         print
  387.         os.system("stty echo")
  388.  
  389.  
  390. # Utility functions
  391.  
  392. # Return the IP address of the magic hostname 'localhost'
  393. _localhost = None
  394. def localhost():
  395.     global _localhost
  396.     if not _localhost:
  397.         _localhost = socket.gethostbyname('localhost')
  398.     return _localhost
  399.  
  400. # Return the IP address of the current host
  401. _thishost = None
  402. def thishost():
  403.     global _thishost
  404.     if not _thishost:
  405.         _thishost = socket.gethostbyname(socket.gethostname())
  406.     return _thishost
  407.  
  408. # Return the set of errors raised by the FTP class
  409. _ftperrors = None
  410. def ftperrors():
  411.     global _ftperrors
  412.     if not _ftperrors:
  413.         import ftplib
  414.         _ftperrors = (ftplib.error_reply,
  415.                   ftplib.error_temp,
  416.                   ftplib.error_perm,
  417.                   ftplib.error_proto)
  418.     return _ftperrors
  419.  
  420. # Return an empty mimetools.Message object
  421. _noheaders = None
  422. def noheaders():
  423.     global _noheaders
  424.     if not _noheaders:
  425.         import mimetools
  426.         import StringIO
  427.         _noheaders = mimetools.Message(StringIO.StringIO(), 0)
  428.         _noheaders.fp.close()    # Recycle file descriptor
  429.     return _noheaders
  430.  
  431.  
  432. # Utility classes
  433.  
  434. # Class used by open_ftp() for cache of open FTP connections
  435. class ftpwrapper:
  436.     def __init__(self, user, passwd, host, port, dirs):
  437.         self.user = unquote(user or '')
  438.         self.passwd = unquote(passwd or '')
  439.         self.host = host
  440.         self.port = port
  441.         self.dirs = []
  442.         for dir in dirs:
  443.             self.dirs.append(unquote(dir))
  444.         self.init()
  445.     def init(self):
  446.         import ftplib
  447.         self.ftp = ftplib.FTP()
  448.         self.ftp.connect(self.host, self.port)
  449.         self.ftp.login(self.user, self.passwd)
  450.         for dir in self.dirs:
  451.             self.ftp.cwd(dir)
  452.     def retrfile(self, file, type):
  453.         import ftplib
  454.         if type in ('d', 'D'): cmd = 'TYPE A'; isdir = 1
  455.         else: cmd = 'TYPE ' + type; isdir = 0
  456.         try:
  457.             self.ftp.voidcmd(cmd)
  458.         except ftplib.all_errors:
  459.             self.init()
  460.             self.ftp.voidcmd(cmd)
  461.         conn = None
  462.         if file and not isdir:
  463.             try:
  464.                 cmd = 'RETR ' + file
  465.                 conn = self.ftp.transfercmd(cmd)
  466.             except ftplib.error_perm, reason:
  467.                 if reason[:3] != '550':
  468.                     raise IOError, ('ftp error', reason)
  469.         if not conn:
  470.             # Try a directory listing
  471.             if file: cmd = 'LIST ' + file
  472.             else: cmd = 'LIST'
  473.             conn = self.ftp.transfercmd(cmd)
  474.         return addclosehook(conn.makefile('rb'), self.ftp.voidresp)
  475.  
  476. # Base class for addinfo and addclosehook
  477. class addbase:
  478.     def __init__(self, fp):
  479.         self.fp = fp
  480.         self.read = self.fp.read
  481.         self.readline = self.fp.readline
  482.         self.readlines = self.fp.readlines
  483.         self.fileno = self.fp.fileno
  484.     def __repr__(self):
  485.         return '<%s at %s whose fp = %s>' % (
  486.               self.__class__.__name__, `id(self)`, `self.fp`)
  487.     def close(self):
  488.         self.read = None
  489.         self.readline = None
  490.         self.readlines = None
  491.         self.fileno = None
  492.         if self.fp: self.fp.close()
  493.         self.fp = None
  494.  
  495. # Class to add a close hook to an open file
  496. class addclosehook(addbase):
  497.     def __init__(self, fp, closehook, *hookargs):
  498.         addbase.__init__(self, fp)
  499.         self.closehook = closehook
  500.         self.hookargs = hookargs
  501.     def close(self):
  502.         if self.closehook:
  503.             apply(self.closehook, self.hookargs)
  504.             self.closehook = None
  505.             self.hookargs = None
  506.         addbase.close(self)
  507.  
  508. # class to add an info() method to an open file
  509. class addinfo(addbase):
  510.     def __init__(self, fp, headers):
  511.         addbase.__init__(self, fp)
  512.         self.headers = headers
  513.     def info(self):
  514.         return self.headers
  515.  
  516. # class to add info() and geturl() methods to an open file
  517. class addinfourl(addbase):
  518.     def __init__(self, fp, headers, url):
  519.         addbase.__init__(self, fp)
  520.         self.headers = headers
  521.         self.url = url
  522.     def info(self):
  523.         return self.headers
  524.     def geturl(self):
  525.         return self.url
  526.  
  527.  
  528. # Utility to combine a URL with a base URL to form a new URL
  529.  
  530. def basejoin(base, url):
  531.     type, path = splittype(url)
  532.     if type:
  533.         # if url is complete (i.e., it contains a type), return it
  534.         return url
  535.     host, path = splithost(path)
  536.     type, basepath = splittype(base) # inherit type from base
  537.     if host:
  538.         # if url contains host, just inherit type
  539.         if type: return type + '://' + host + path
  540.         else:
  541.             # no type inherited, so url must have started with //
  542.             # just return it
  543.             return url
  544.     host, basepath = splithost(basepath) # inherit host
  545.     basepath, basetag = splittag(basepath) # remove extraneuous cruft
  546.     basepath, basequery = splitquery(basepath) # idem
  547.     if path[:1] != '/':
  548.         # non-absolute path name
  549.         if path[:1] in ('#', '?'):
  550.             # path is just a tag or query, attach to basepath
  551.             i = len(basepath)
  552.         else:
  553.             # else replace last component
  554.             i = string.rfind(basepath, '/')
  555.         if i < 0:
  556.             # basepath not absolute
  557.             if host:
  558.                 # host present, make absolute
  559.                 basepath = '/'
  560.             else:
  561.                 # else keep non-absolute
  562.                 basepath = ''
  563.         else:
  564.             # remove last file component
  565.             basepath = basepath[:i+1]
  566.         path = basepath + path
  567.     if type and host: return type + '://' + host + path
  568.     elif type: return type + ':' + path
  569.     elif host: return '//' + host + path # don't know what this means
  570.     else: return path
  571.  
  572.  
  573. # Utilities to parse URLs (most of these return None for missing parts):
  574. # unwrap('<URL:type://host/path>') --> 'type://host/path'
  575. # splittype('type:opaquestring') --> 'type', 'opaquestring'
  576. # splithost('//host[:port]/path') --> 'host[:port]', '/path'
  577. # splituser('user[:passwd]@host[:port]') --> 'user[:passwd]', 'host[:port]'
  578. # splitpasswd('user:passwd') -> 'user', 'passwd'
  579. # splitport('host:port') --> 'host', 'port'
  580. # splitquery('/path?query') --> '/path', 'query'
  581. # splittag('/path#tag') --> '/path', 'tag'
  582. # splitattr('/path;attr1=value1;attr2=value2;...') ->
  583. #   '/path', ['attr1=value1', 'attr2=value2', ...]
  584. # splitvalue('attr=value') --> 'attr', 'value'
  585. # splitgophertype('/Xselector') --> 'X', 'selector'
  586. # unquote('abc%20def') -> 'abc def'
  587. # quote('abc def') -> 'abc%20def')
  588.  
  589. def unwrap(url):
  590.     url = string.strip(url)
  591.     if url[:1] == '<' and url[-1:] == '>':
  592.         url = string.strip(url[1:-1])
  593.     if url[:4] == 'URL:': url = string.strip(url[4:])
  594.     return url
  595.  
  596. _typeprog = regex.compile('^\([^/:]+\):\(.*\)$')
  597. def splittype(url):
  598.     if _typeprog.match(url) >= 0: return _typeprog.group(1, 2)
  599.     return None, url
  600.  
  601. _hostprog = regex.compile('^//\([^/]+\)\(.*\)$')
  602. def splithost(url):
  603.     if _hostprog.match(url) >= 0: return _hostprog.group(1, 2)
  604.     return None, url
  605.  
  606. _userprog = regex.compile('^\([^@]*\)@\(.*\)$')
  607. def splituser(host):
  608.     if _userprog.match(host) >= 0: return _userprog.group(1, 2)
  609.     return None, host
  610.  
  611. _passwdprog = regex.compile('^\([^:]*\):\(.*\)$')
  612. def splitpasswd(user):
  613.     if _passwdprog.match(user) >= 0: return _passwdprog.group(1, 2)
  614.     return user, None
  615.  
  616. _portprog = regex.compile('^\(.*\):\([0-9]+\)$')
  617. def splitport(host):
  618.     if _portprog.match(host) >= 0: return _portprog.group(1, 2)
  619.     return host, None
  620.  
  621. # Split host and port, returning numeric port.
  622. # Return given default port if no ':' found; defaults to -1.
  623. # Return numerical port if a valid number are found after ':'.
  624. # Return None if ':' but not a valid number.
  625. _nportprog = regex.compile('^\(.*\):\(.*\)$')
  626. def splitnport(host, defport=-1):
  627.     if _nportprog.match(host) >= 0:
  628.         host, port = _nportprog.group(1, 2)
  629.         try:
  630.         if not port: raise string.atoi_error, "no digits"
  631.         nport = string.atoi(port)
  632.         except string.atoi_error:
  633.         nport = None
  634.         return host, nport
  635.     return host, defport
  636.  
  637. _queryprog = regex.compile('^\(.*\)\?\([^?]*\)$')
  638. def splitquery(url):
  639.     if _queryprog.match(url) >= 0: return _queryprog.group(1, 2)
  640.     return url, None
  641.  
  642. _tagprog = regex.compile('^\(.*\)#\([^#]*\)$')
  643. def splittag(url):
  644.     if _tagprog.match(url) >= 0: return _tagprog.group(1, 2)
  645.     return url, None
  646.  
  647. def splitattr(url):
  648.     words = string.splitfields(url, ';')
  649.     return words[0], words[1:]
  650.  
  651. _valueprog = regex.compile('^\([^=]*\)=\(.*\)$')
  652. def splitvalue(attr):
  653.     if _valueprog.match(attr) >= 0: return _valueprog.group(1, 2)
  654.     return attr, None
  655.  
  656. def splitgophertype(selector):
  657.     if selector[:1] == '/' and selector[1:2]:
  658.         return selector[1], selector[2:]
  659.     return None, selector
  660.  
  661. try:
  662.     from urlop import *
  663. except ImportError:
  664.     _quoteprog = regex.compile('%[0-9a-fA-F][0-9a-fA-F]')
  665.     def unquote(s):
  666.         i = 0
  667.         n = len(s)
  668.         res = []
  669.         while 0 <= i < n:
  670.             j = _quoteprog.search(s, i)
  671.             if j < 0:
  672.                 res.append(s[i:])
  673.                 break
  674.             res.append(s[i:j] + chr(string.atoi(s[j+1:j+3], 16)))
  675.             i = j+3
  676.         return string.joinfields(res, '')
  677.  
  678.     always_safe = string.letters + string.digits + '_,.-'
  679.     def quote(s, safe = '/'):
  680.         safe = always_safe + safe
  681.         res = []
  682.         for c in s:
  683.             if c in safe:
  684.                 res.append(c)
  685.             else:
  686.                 res.append('%%%02x' % ord(c))
  687.         return string.joinfields(res, '')
  688.  
  689.  
  690. # Proxy handling
  691. def getproxies():
  692.     """Return a dictionary of protocol scheme -> proxy server URL mappings.
  693.  
  694.     Scan the environment for variables named <scheme>_proxy;
  695.     this seems to be the standard convention.  If you need a
  696.     different way, you can pass a proxies dictionary to the
  697.     [Fancy]URLopener constructor.
  698.  
  699.     """
  700.     proxies = {}
  701.     for name, value in os.environ.items():
  702.         if value and name[-6:] == '_proxy':
  703.             proxies[name[:-6]] = value
  704.     return proxies
  705.  
  706.  
  707. # Test and time quote() and unquote()
  708. def test1():
  709.     import time
  710.     s = ''
  711.     for i in range(256): s = s + chr(i)
  712.     s = s*4
  713.     t0 = time.time()
  714.     qs = quote(s)
  715.     uqs = unquote(qs)
  716.     t1 = time.time()
  717.     if uqs != s:
  718.         print 'Wrong!'
  719.     print `s`
  720.     print `qs`
  721.     print `uqs`
  722.     print round(t1 - t0, 3), 'sec'
  723.  
  724.  
  725. # Test program
  726. def test():
  727.     import sys
  728.     import regsub
  729.     args = sys.argv[1:]
  730.     if not args:
  731.         args = [
  732.             '/etc/passwd',
  733.             'file:/etc/passwd',
  734.             'file://localhost/etc/passwd',
  735.             'ftp://ftp.cwi.nl/etc/passwd',
  736.             'gopher://gopher.cwi.nl/11/',
  737.             'http://www.cwi.nl/index.html',
  738.             ]
  739.     try:
  740.         for url in args:
  741.             print '-'*10, url, '-'*10
  742.             fn, h = urlretrieve(url)
  743.             print fn, h
  744.             if h:
  745.                 print '======'
  746.                 for k in h.keys(): print k + ':', h[k]
  747.                 print '======'
  748.             fp = open(fn, 'r')
  749.             data = fp.read()
  750.             del fp
  751.             print regsub.gsub('\r', '', data)
  752.             fn, h = None, None
  753.         print '-'*40
  754.     finally:
  755.         urlcleanup()
  756.  
  757. # Run test program when run as a script
  758. if __name__ == '__main__':
  759. ##    test1()
  760.     test()
  761.